/*
 * @Author: 黄威
 * @Date: 2021-08-13 14:51:00
 * @LastEditors: 黄威
 * @LastEditTime: 2022-02-10 14:12:41
 * @Description: 图层选择器
 */
import L from 'leaflet'
import * as Cesium from 'Cesium'
import { LGraphicLayer, C3GraphicLayer } from '@/utils/bind-map/graphic'

import type { GraphicLayer } from 'types'

declare type SelectEvent =
    | 'click'
    | 'dblclick'
    | 'mousedown'
    | 'mouseup'
    | 'mouseover'
    | 'mouseout'
    | 'mouseenter'
    | 'mouseleave'
    | 'mousemove'
    | 'contextmenu'
    | 'preclick'

const EventMapper: { [key: string]: Cesium.ScreenSpaceEventType } = {
    click: Cesium.ScreenSpaceEventType.LEFT_CLICK,
    dblclick: Cesium.ScreenSpaceEventType.RIGHT_CLICK,
    mousedown: Cesium.ScreenSpaceEventType.LEFT_DOWN,
    mouseup: Cesium.ScreenSpaceEventType.LEFT_UP,
    mousemove: Cesium.ScreenSpaceEventType.MOUSE_MOVE,
    contextmenu: Cesium.ScreenSpaceEventType.RIGHT_CLICK,
}

const Evt = L.Evented.extend({})

abstract class SelectHandler<T> {
    protected evt: L.Evented
    protected map: T
    private _delegatedListeners: any

    constructor(map: T) {
        this.evt = new Evt()
        this.map = map
        this._init(map)
    }

    abstract _init(map: T): void
    abstract _createDelegatedListener(
        type: SelectEvent,
        layer: GraphicLayer,
        listener: any,
        context?: any
    ): {
        layer: GraphicLayer
        listener: any
        delegates: { [key: string]: (evt: any) => void }
        context: any
    }

    on(
        type: SelectEvent,
        layer: GraphicLayer,
        listener: (...rest: any[]) => void,
        context?: any
    ) {
        const delegatedListener = this._createDelegatedListener(
            type,
            layer,
            listener,
            context
        )

        this._delegatedListeners = this._delegatedListeners || {}
        this._delegatedListeners[type] = this._delegatedListeners[type] || []
        this._delegatedListeners[type].push(delegatedListener)

        for (const event in delegatedListener.delegates) {
            this.evt.on(event, delegatedListener.delegates[event])
        }
    }

    once(
        type: SelectEvent,
        layer: GraphicLayer,
        listener: (...rest: any[]) => void,
        context?: any
    ) {
        const delegatedListener = this._createDelegatedListener(
            type,
            layer,
            listener,
            context
        )

        for (const event in delegatedListener.delegates) {
            this.evt.once(event, delegatedListener.delegates[event])
        }
    }

    off(
        type: SelectEvent,
        layer: GraphicLayer,
        listener?: (...rest: any[]) => void,
        context?: any
    ) {
        const removeDelegatedListener = (delegatedListeners: any) => {
            const listeners = delegatedListeners[type]

            for (let i = 0; i < listeners.length; i++) {
                const delegatedListener = listeners[i]
                if (
                    delegatedListener.layer === layer &&
                    delegatedListener.listener === listener &&
                    delegatedListener.context === context
                ) {
                    for (const event in delegatedListener.delegates) {
                        this.evt.off(event, delegatedListener.delegates[event])
                    }
                    listeners.splice(i, 1)
                    return this
                }
            }
        }

        if (this._delegatedListeners && this._delegatedListeners[type]) {
            removeDelegatedListener(this._delegatedListeners)
        }
    }
}

let mouseDownTime = 0,
    mouseUpTime = 0
export class LSelectHandler extends SelectHandler<L.Map> {
    _init(map: any) {
        const self = this
        const originFn = map.__proto__._fireDOMEvent

        map._fireDOMEvent = function (e: any, type: string, targets: any[]) {
            originFn.call(map, e, type, targets)

            if (type === 'mousedown') mouseDownTime = e.timeStamp
            if (type === 'mouseup') mouseUpTime = e.timeStamp

            // 点击时长大于500毫秒，返回
            if (type === 'click') {
                const timeStmp = mouseUpTime - mouseDownTime
                if (timeStmp > 500) return
            }

            // 未监听
            if (!self.evt.listens(type)) return

            // 代理事件
            if (e.target && e.target._leaflet_id) {
                targets = (targets || []).concat(
                    self._findEventTargets(e, type)
                )

                const graphics = []
                for (let i = 0; i < targets.length; i++) {
                    const target = targets[i]
                    if (target._graphic) graphics.push(target._graphic)
                }
                self.evt.fire(type, { graphics, position: e })
            }
        }
    }

    _createDelegatedListener(
        type: SelectEvent,
        layer: GraphicLayer,
        listener: any,
        context?: any
    ) {
        const delegate = (evt: any) => {
            delete evt.target
            delete evt.sourceTarget
            evt.graphics = evt.graphics.filter(
                (g: any) => !layer || LGraphicLayer.wrap(layer).has(g)
            )
            listener.call(context, evt)
        }
        return { layer, listener, delegates: { [type]: delegate }, context }
    }

    _findEventTargets(e: any, type: any) {
        let targets = [],
            target,
            src = e.target || e.srcElement,
            dragging = false
        const isHover = type === 'mouseout' || type === 'mouseover'

        while (src) {
            target = (this.map as any)._targets[L.Util.stamp(src)]
            if (
                target &&
                (type === 'click' || type === 'preclick') &&
                !e._simulated &&
                (this.map as any)._draggableMoved(target)
            ) {
                // Prevent firing click after you just dragged an object.
                dragging = true
                break
            }
            if (target) {
                if (isHover && !(L.DomEvent as any).isExternalTarget(src, e)) {
                    break
                }
                targets.push(target)
                if (isHover) {
                    break
                }
            }
            if (src === (this.map as any)._container) {
                break
            }
            src = src.parentNode
        }
        if (
            !targets.length &&
            !dragging &&
            !isHover &&
            (L.DomEvent as any).isExternalTarget(src, e)
        ) {
            targets = []
        }
        return targets
    }
}

export class CesiumSelectHandler extends SelectHandler<Cesium.Viewer> {
    private querySelectedGraphics(evt: any, layer: GraphicLayer) {
        delete evt.target
        delete evt.sourceTarget

        const fs = this.map.scene.drillPick(evt.position)
        const graphics = []
        for (let i = 0; i < fs.length; i++) {
            const entity = fs[i].id
            if (
                entity &&
                entity._graphic &&
                (!layer || C3GraphicLayer.wrap(layer).has(entity._graphic))
            ) {
                graphics.push(entity._graphic)
            }
        }
        evt.graphics = graphics

        return graphics
    }

    _init(map: Cesium.Viewer) {
        const self = this
        const eventHandler = new Cesium.ScreenSpaceEventHandler(map.canvas)
        for (const key in EventMapper) {
            const eventType = EventMapper[key]

            eventHandler.setInputAction(function (evt) {
                // 如果存在事件监听，触发（提高渲染性能）
                if (self.evt.listens(key)) {
                    self.evt.fire(key, {
                        position: evt.position || evt.endPosition,
                    })
                }
            }, eventType as any)
        }
    }

    _createDelegatedListener(
        type: SelectEvent,
        layer: GraphicLayer,
        listener: any,
        context?: any
    ) {
        if (type === 'mouseenter' || type === 'mouseover') {
            let mousein = false
            const mousemove = (e: any) => {
                const graphics = this.querySelectedGraphics(e, layer)
                if (!graphics.length) {
                    mousein = false
                } else if (!mousein) {
                    mousein = true
                    listener.call(context, { ...e, type })
                }
            }
            const mouseout = () => {
                mousein = false
            }
            return {
                layer,
                listener,
                delegates: { mousemove, mouseout },
                context,
            }
        } else if (type === 'mouseleave' || type === 'mouseout') {
            let mousein = false
            const mousemove = (e: any) => {
                const features = this.querySelectedGraphics(e, layer)
                if (features.length) {
                    mousein = true
                } else if (mousein) {
                    mousein = false
                    listener.call(context, { ...e, type })
                }
            }
            const mouseout = (e: any) => {
                if (mousein) {
                    mousein = false
                    listener.call(context, { ...e, type })
                }
            }
            return {
                layer,
                listener,
                delegates: { mousemove, mouseout },
                context,
            }
        } else {
            const delegate = (evt: any) => {
                this.querySelectedGraphics(evt, layer)
                listener.call(context, evt)
            }
            return { layer, listener, delegates: { [type]: delegate }, context }
        }
    }
}
